package com.diy.sigmund.diycommon.spring;

import com.alibaba.ttl.TransmittableThreadLocal;
import com.diy.sigmund.diycommon.entity.CustomUser;
import com.diy.sigmund.diycommon.spring.advice.GlobalExceptionHandler;
import com.yomahub.tlog.web.interceptor.TLogWebInvokeTimeInterceptor;
import org.apache.commons.lang3.time.StopWatch;

/**
 * 使用ThreadLocal存放用户信息
 * https://blog.csdn.net/qq_39632561/article/details/115425564
 *
 * @author ylm-sigmund
 * @since 2021/8/16 16:12
 */
public class ContextHolder {

    /**
     * 保存用户对象的ThreadLocal  在拦截器操作 设置、删除相关用户数据
     */
    private final ThreadLocal<CustomUser> userThreadLocal;
    /**
     * 运行时间统计,用于全局异常处理器
     * {@link GlobalExceptionHandler}
     *
     * @see TLogWebInvokeTimeInterceptor
     */
    private final TransmittableThreadLocal<StopWatch> invokeTimeTL;

    /**
     * 私有化构造方法
     * <br>{@link TransmittableThreadLocal}({@code TTL})可以将值从提交任务的线程传输到执行任务的线程。
     * <br>注意： TransmittableThreadLocal扩展了InheritableThreadLocal ，因此TransmittableThreadLocal首先是InheritableThreadLocal 。 如果InheritableThreadLocal的可继承能力有潜在的泄漏问题，您可以禁用可继承能力：
     * <br>❶ 对于线程池组件（ java.util.concurrent.ThreadPoolExecutor ， java.util.concurrent.ForkJoinPool ），永远不应该发生可继承特性，因为线程池组件中的线程是预先创建和池化的，这些线程对商业逻辑是中立的/数据。 通过使用方法getDisableInheritableThreadFactory / getDefaultDisableInheritableForkJoinWorkerThreadFactory包装线程工厂来禁用线程池组件的可继承。 或者您可以通过com.alibaba.ttl.threadpool.agent.TtlAgent开启“禁用线程池可继承”，以便自动透明地为线程池组件包装线程工厂。
     * <br>❷ 在其他情况下，通过覆盖方法childValue(Object)禁用可继承。 该值是否可继承可由数据所有者控制，当数据所有者有明确的想法时，请谨慎禁用它。
     *
     * <br> TransmittableThreadLocal<String> transmittableThreadLocal = new TransmittableThreadLocal<>() {
     * <br>     protected String childValue(String parentValue) {
     * <br>         return initialValue();
     * <br>     }
     * <br> }
     */
    private ContextHolder() {
        // 无法传递到子线程
        // this.userThreadLocal = new ThreadLocal<>();

        // 只有首次使用被创建的时候能正确复制父线程的上下文
        // this.userThreadLocal = new InheritableThreadLocal<>();

        // 异步线程传递上下文，可在异步线程池中获取上下文信息,可传输的线程本地,`TransmittableThreadLocal`
        this.userThreadLocal = new TransmittableThreadLocal<>();
        this.invokeTimeTL = new TransmittableThreadLocal<>();
    }

    /**
     * 对外提供获取实例的公共方法
     *
     * @return ContextHolder
     */
    public static ContextHolder getInstance() {
        return InnerClass.INSTANCE;
    }

    /**
     * 静态内部类实现单例
     */
    private static class InnerClass {

        private static final ContextHolder INSTANCE = new ContextHolder();
    }

    /**
     * 添加当前登录用户方法  在拦截器方法执行前调用设置获取用户
     *
     * @param customUser User
     */
    public void setCustomUser(CustomUser customUser) {
        userThreadLocal.set(customUser);
    }

    /**
     * 获取当前登录用户方法
     */
    public CustomUser getCustomUser() {
        return userThreadLocal.get();
    }

    public void setStopWatch() {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        invokeTimeTL.set(stopWatch);
    }

    public StopWatch getStopWatch() {
        return invokeTimeTL.get();
    }

    /**
     * 删除上下文  在拦截器方法执行后
     */
    public void remove() {
        userThreadLocal.remove();
        invokeTimeTL.remove();
    }

}
